        SUBT    > Sources.FSPath
 [ Version >= 170
; The entire contents of this file were introduced in version 1.70

; These routines are concerned with the management of resolving a path
;
;
;
; To use:
; Call FSPath_Init_<Something>
; Use FSPath_LengthOfNextPrefix to get the length of the next prefix
; Use FSPath_CopyNextPrefix to copy it
; Use FSPath_StepContext to move to the next prefix
; Check context word for being Nowt to check when finished (after a StepContext)
; Use FSPath_AllDone to test whether there's another element in the path after this one
; Use FSPath_DisposeContext to junk the context when you've done with it
;

;
; The following routines initialise a path context.
; 
; FSPath_Init_Variable does an FSPath_Init_VariableWithDefault with
; a default of an empty string.
;
; FSPath_Init_VariableWithDefault looks up the given variable, and
; uses the string result if the variable existed, otherwise it uses
; the default given string in FSPath_Init_String, and registers
; which variable was used (if any).
;

; The root structure of a path enumeration context
        ^       0
FSPathEnum_Next         #       4
FSPathEnum_VarName      #       4
FSPathEnum_VarVal       #       4
FSPathEnum_size         #       0

;
; FSPath_Init_Variable
;
; In    r0 = workspace word of path search context
;       r1 = path^ of path going to be un-pathed
;       r2 = pointer to name of path variable
;       r4 = flags:
;               bit     meaning when set
;               0       Don't allow mutli-element paths (for write operations)
;
FSPath_Init_Variable ENTRY      "r0,r2,r3,r4,r5,r6", 4
        addr    r3, anull
        B       %FT10

;
; FSPath_Init_String
;
; In    r0 = workspace of path search context
;       r1 = path^ of path going to be un-pathed
;       r3 = path string
;       r4 = flags:
;               bit     meaning when set
;               0       Don't allow mutli-element paths (for write operations)
;
FSPath_Init_String ALTENTRY
        MOV     r2, #NULL
        B       %FT10

;
; FSPath_Init_VariableWithDefault
;
; In    r0 = workspace word of path search context
;       r1 = path^ of path going to be un-pathed
;       r2 = pointer to name of path variable (NULL
;               to indicate don't even try to get the variable)
;       r3 = string^ of default if variable doesn't exist
;       r4 = flags:
;               bit     meaning when set
;               0       Don't allow multi-element paths (for write operations)
;
; Out   r1 advanced past <path>: if present and resolved to <path>$Path
;
FSPathConst_NoMultiparts * 1
;
FSPath_Init_VariableWithDefault ALTENTRY
10
        ; Hold original start of path
        MOV     r6, r1

        ; Zero the context
        MOV     r14, #Nowt
        STR     r14, [r0]

        Push    "r2,r3"
        BL      FSPath_TestForPathLikePrefix
        ADDVS   sp, sp, #2*4
        EXIT    VS

        MOVS    r5, r3
        Pull    "r2,r3",EQ
        BEQ     %FT20

        ; Found it's <path>:, let's switch to using that
        ADD     r5, r5, #2*4            ; To drop r2,r3 from stack when frame is junked
        MOV     r1, r2                  ; r1 = after the <path>: bit
        MOV     r2, sp                  ; r2 = <path>$Path on stack
        MOV     r3, #NULL               ; r3 = default forced to none if <path>: given in user's filepath.

20
        ; Having checked for <path>: on the user's path we have:
        ; r0 = pathenum context
        ; r1 = pointer within user's file path beyond <path>: if it is present
        ; r2 -> <path>$Path or sysvar name or NULL
        ; r3 -> default or NULL
        ; r4 = flags
        ; r5 = sp adjust after using <path>$Path

 [ debugpath
 DSTRING r1,"FSPath tail is "
 ]

        ; Get to the first path piece
        BL      FSPath_AddVarToContext
        ADD     sp, sp, r5
        EXIT    VS

        ; Path didn't go off, so just return
        LDR     r5, [r0]
        TEQ     r5, #Nowt
        MOVEQ   r1, r6
        EXIT    EQ

        ; Resolve the 1st path element
        BL      FSPath_ResolveContext

        ; Check for fussiness about multipart path vars (generally for write operations)
        TST     r4, #FSPathConst_NoMultiparts
        EXIT    EQ

        ; Check path expansion doesn't contain multi-part paths
        SUB     r0, r0, #FSPathEnum_Next
30
        LDR     r0, [r0, #FSPathEnum_Next]
        TEQ     r0, #Nowt
        EXIT    EQ
        LDR     r2, [r0, #FSPathEnum_VarVal]
40
        LDRB    r14, [r2], #1
        TEQ     r14, #0
        BEQ     %BT30
        TEQ     r14, #","
        BNE     %BT40

        ; Found a , hence must be a multipart path - naughty!
        addr    r0, ErrorBlock_MultipartPathUsed
        BL      CopyError
        EXIT

;
; FSPath_DisposeContext
;
; In    r0 = workspace word of path search context
;
FSPath_DisposeContext ENTRY "r1"
10
        LDR     r1, [r0]
        TEQ     r1, #Nowt
        EXIT    EQ
        LDR     r1, [r1, #FSPathEnum_Next]
        BL      SFreeLinkedArea
        STR     r1, [r0]
        BVC     %BT10
        EXIT

;
; FSPath_FindFSPrefix
;
; In    r1 = path^ which may have an FS prefix
;
; Out   r1 = path^ advanced to after the FS prefix
;       r2 = start of FS prefix (0 if no prefix)
;       r3 = length of FS prefix (0 if no prefix)
;
FSPath_FindFSPrefix ENTRY "r0,r1"
        MOV     r2, #0
        MOV     r3, #0

        ; If starts :, then it must be :<disc>.blah
        LDRB    r0, [r1]
        TEQ     r0, #":"
        EXIT    EQ

        ; Does it start -, hence it's probably -fs-
        TEQ     r0, #"-"
        ADDEQ   r1, r1, #1
        BEQ     %FT10

        ; if doesn't start with an alphanumeric, it can't be <fs>:
        BL      IsAlphanumeric
        BCS     %FT05

        ; But, it might be #<special>:
        TEQ     r0, #"#"
        EXIT    NE

05
        MOV     r0, #":"

10
        ; r0 = term to find (: or -)
        MOV     r2, r1
        BL      LookForAGoodTerm
        MOVNE   r2, #0
        EXIT    NE

        ; Find the length of the FS prefix
20
        LDRB    r14, [r2, r3]
        TEQ     r14, r0
        ADDNE   r3, r3, #1
        BNE     %BT20

        ; point r1 beyond the terminator
        ADD     r1, r2, r3
        ADD     r1, r1, #1
        STR     r1, [sp, #1*4]

        EXIT

;
; FSPath_ExtractFS
;
; In    r0 = 0 don't generate error if FS prefix isn't an existing filing system
;          <>0 generate error
;       r2 = Start of FS prefix (0 if none present)
;       r3 = Length of FS prefix (0 if none present)
;
; Out   r2 = 0 if no filing system specified, <> 0 otherwise
;       r3 = location of special field
;       r4 = length of special field
;       fscb^ (Nowt if FS prefix and tempfs not set) (ie not Nowt if r2 <> 0)
;
str_dollarpath_message  DCB     "$Path_Message", 0
                        ALIGN
FSPath_ExtractFS ENTRY  "r0,r1,r5,r6"

        ; Save type of error to generate
        MOV     r5, r0

        TEQ     r2, #NULL
        MOVEQ   r3, #NULL
        MOVEQ   r4, #0
        BLEQ    ReadTempFS
        EXIT    VS
        MOVEQ   fscb, r0
        EXIT    EQ

        MOV     r4, r3
        MOV     r3, #0
        MOV     r1, r2
        LDRB    r0, [r1]
        TEQ     r0, #"#"
        MOVEQ   r2, #0
        BLEQ    ReadTempFS
        EXIT    VS
        MOVEQ   fscb, r0
        BEQ     %FT50

        MOV     r0, #0
        BL      LookupFSName
        TEQ     fscb, #Nowt
        MOVNE   r2, #-1
        BNE     %FT50

        ; Filing system not found
        TEQ     r5, #0
        BEQ     %FT30

        ; Generate a more interesting path type of error
        MOV     r5, sp

        ADD     r0, r3, #?str_dollarpath_message + 3
        BIC     r0, r0, #3
        SUB     sp, sp, r0

        MOV     r2, r3
10
        SUBS    r2, r2, #1
        LDRPLB  lr, [r1, r2]
        STRPLB  lr, [sp, r2]
        BPL     %BT10

        MOV     r4, r1
        MOV     r6, r3
        ADD     r1, sp, r3
        addr    r2, str_dollarpath_message
        BL      strcpy
        MOV     r0, sp
        addr    r3, anull
        BL      SReadVariableToBuffer
        MOV     r1, r4
        MOV     r3, r6
        BVS     %FT20           ; Variable didn't read
        LDRB    lr, [sp]
        TEQ     lr, #0
        BEQ     %FT20           ; Variable read and was or defaulted to the nul string, hence generate a more useful error

        ; Substitute the read variable into a blank error
        MOV     r1, sp
        addr    r0, MagicErrorBlock_UkFS
        BL      MagicCopyError
        MOV     sp, r5
        EXIT

20
        ; Resort to Filing system or path 'Daft:' not present
        addr    r0, MagicErrorBlock_UnknownFilingSystemPath
        BL      MagicCopyErrorSpan
        MOV     sp, r5
        EXIT

30
        ; Filing system not present, but don't want to complain about it
        MOV     r2, #0          ; No FS specified

50
        LDRB    r0, [r1]
        TEQ     r0, #"#"
        MOVNE   r3, #0
        MOVNE   r4, #0
        EXIT    NE

        ; Special field present - calculate the size and location of it
        SUB     r4, r4, r3
        SUB     r4, r4, #1
        ADD     r3, r1, #1
        EXIT

;
; FSPath_TestForPathLikePrefix
;
; In    r1 = path^ which may have <path>: - like prefix
;
; Out   r1 unchanged
;       r2 Points beyond <path>: if it was present
;       r3 = stack adjust to give stack back (is 0 if no prefix present)
;       <path>$Path string on stack
;       FileSwitch$SpecialField set to special field of prefix
;
FileSwitchSpecialFieldVar DCB "FileSwitch$SpecialField", 0
DollarPathSuffix DCB "$Path", 0
        ALIGN

FSPath_TestForPathLikePrefix ENTRY "r0,r1,r2,r4"
 [ debugpath
        DSTRING r1,"Checking for a <path>: prefix on "
 ]

        BL      FSPath_FindFSPrefix
; Out   r1 = path^ advanced to after the FS prefix
;       r2 = start of FS prefix (0 if no prefix)
;       r3 = length of FS prefix (0 if no prefix)
 [ debugpath
        DSTRING r1, "After FS prefix "
        DSTRING r2, "start of fs prefix "
        DREG    r3, "length of FS prefix "
 ]
        STR     r1, [sp, #2*4]          ; r2 out

        ; Was there a prefix?
        TEQ     r3, #0
        EXIT    EQ

        MOV     r4, #0
10
        CMP     r4, r3
        BHS     %FT20
        LDRB    lr, [r2, r4]
        TEQ     lr, #"#"
        ADDNE   r4, r4, #1
        BNE     %BT10

20
        ; r1+r4 points at a '#' or r4=r3 if no #, r1+r3 points after end of <path>#<special>
        ; Also, if no special field then r3 equals r4

        ; Set FileSwitch$SpecialField
        LDRB    lr, [r2, r3]
        Push    "r2,r3,r4,lr"
        MOV     lr, #0
        STRB    lr, [r2, r3]
        addr    r0, FileSwitchSpecialFieldVar
        ADD     r4, r4, #1
        ADD     r1, r2, r4
        SUBS    r2, r3, r4
        SUBMI   r1, r1, #1      ; corrections in the absent special field case
        MOVMI   r2, #0
        MOV     r3, #0
        MOV     r4, #VarType_String
 [ debugpath
        DSTRING r0, "Special field var is "
        DSTRING r1, "Special field is "
        DREG    r2, "Length "
        DREG    r3, "Call "
        DREG    r4, "type "
 ]
        SWI     XOS_SetVarVal
        Pull    "r2,r3,r4,lr"
        BVS     %FT90
        STRB    lr, [r2, r3]

        ; Generate a <FS>$Path on the stack - correcting for presence of absence of #<special>
        MOV     r0, r4
        ADD     r3, r0, #?DollarPathSuffix +3   ; Includes terminator
        BIC     r3, r3, #3

 [ debugpath
        DREG    r3, "Bytes reserved for prefix:",cc
        DREG    r2, " given start at ",cc
        DREG    r0, " and length of "
 ]

        ; Create a stack frame below the save frame of this proc
        SUB     sp, sp, r3

        ; Add enough to swallow the entry frame
        ADD     r3, r3, #5*4

        ; At end of <FS> on stack - for later
        ADD     r1, sp, r0

        ; Copy the <path> bit of <path>:
        MOV     r14, #0
50
        STRB    r14, [sp, r0]
        SUBS    r0, r0, #1
        LDRPLB  r14, [r2, r0]
        BPL     %BT50

        ; Append $Path
        addr    r2, DollarPathSuffix
        BL      strcpy

 [ debugpath
        MOV     r0, sp
        DSTRING r0,"Path var on stack is "
 ]

        ADD     r14, sp, r3
        SUB     r14, r14, #5*4
        LDMIA   r14, {$Proc_RegList, pc}

90
        BL      CopyErrorExternal
        EXIT

;
; FSPath_TestVarPresent
;
; In    r1 -> variable name
;
; Out   EQ variable not present
;       NE variable present
;
FSPath_TestVarPresent ENTRY "r0,r1,r2,r3,r4"
        MOV     r0, r1
        MOV     r1, #-1
        MOV     r2, #-1
        MOV     r3, #0
        MOV     r4, #0
        SWI     XOS_ReadVarVal
        CLRV    ; Clear any error 'cos it's just var not found (honest, guv!)
        TST     r2, #&80000000
        EXIT

;
; FSPath_AddVarToContext
;
; In    r0 = workspace word to path search context
;       r2 = Pointer to name of path variable, or NULL
;       r3 = string^ of default if variable doesn't exist, or
;               NULL if shouldn't add to context if doesn't exist
;
; Note: having both r2 and r3 NULL isn't handled (apart from being silly).
;
; Out   Error or var with val attached at beginning of
;       path search context.
;
FSPath_AddVarToContext ENTRY "r0,r1,r2,r3,r4,r5,r6"
 [ debugpath
 DSTRING r2,"Adding variable ",cc
 DSTRING r3," with default "
 ]

        MOV     r6, r0

        ; If variable being obtained then check for recursion
        TEQ     r2, #NULL
        BNE     %FT10

        ; No variable being obtained - set up for copy of default
        MOV     r0, #NULL
        MOV     r1, r3
        MOV     r5, #0
        B       %FT40

10
        ; Check for variable's existance
        MOV     r1, r2
        BL      FSPath_TestVarPresent
        SUBNE   r0, r0, #FSPathEnum_Next
        BNE     %FT30

        ; Variable doesn't exist - check for default
        MOVS    r1, r3
        EXIT    EQ
        MOV     r0, #NULL
        MOV     r5, #0
        B       %FT40

20
        ; Check this variable hasn't already been used
        LDR     r1, [r0, #FSPathEnum_VarName]
        TEQ     r1, #NULL
        BEQ     %FT30
        BL      strcmp
        BNE     %FT30

        ; Already used this path variable in this expansion - complain
        addr    r0, MagicErrorBlock_RecursivePath
        BL      MagicCopyError
        STR     r0, [sp]
        EXIT

        ; Move to next part of path
30
        LDR     r0, [r0, #FSPathEnum_Next]
        TEQ     r0, #Nowt
        BNE     %BT20

        ; Reached end of list of recursions down paths - get the variable
        ; as it's not been used before.
        MOV     r0, r2
        BL      SReadVariableToBuffer
        MOV     r5, r2
        BVS     %FT90

40
        ; r0 = name of variable used (or NULL).
        ; r1 = Value of variable or default
        ; r5 = stack adjust after taking value

        MOV     r3, #FSPathEnum_size + 1 + 1    ; One for each string terminator
        BL      strlen_accumulate
        MOV     r4, r1
        MOV     r1, r0
        BL      strlen_accumulate
        MOV     r0, r6
        LDR     r6, [r0]

        ; Get new element and link it to pathenum root
 [ debugheap
        DLINE   "FSPath_AddVarToContext:",cc
 ]
        BL      SGetLinkedArea
        BVS     %FT90

        ; Link rest of chain to new element
        STR     r6, [r2, #FSPathEnum_Next]
        MOV     r6, r2

        ; Copy the variable's name (or NULL) into the structure
        MOVS    r2, r1
        STREQ   r2, [r6, #FSPathEnum_VarName]
        ADD     r1, r6, #FSPathEnum_size
        STRNE   r1, [r6, #FSPathEnum_VarName]
        BLNE    strcpy_advance
        ADDNE   r1, r1, #1

        ; Copy the variable's value into the structure
        MOV     r2, r4
        BL      strcpy
        STR     r1, [r6, #FSPathEnum_VarVal]
        BL      FSPath_PackPath

90
        ; r5 is the stack offset to correct the stack with
        ADD     sp, sp, r5
        STRVS   r0, [sp]
        EXIT

;
; FSPath_PackPath
;
; In    r1 = Pointer to path string
;
; Out   Path string has had spaces removed
;
FSPath_PackPath ENTRY "r0,r1"
        MOV     r0, r1
10
        LDRB    r14, [r0], #1
        TEQ     r14, #space
        STRNEB  r14, [r1], #1
        CMP     r14, #delete
        CMPNE   r14, #space-1
        BHI     %BT10
        EXITS

;
; FSPath_StepContext
;
; In    r0 = pointer to path search context
;
; Out   Next element of path is presented
;       EQ/NE for done/more
;
; The first element of the pathenum has its first path part
; stripped. If there are no further path parts then the pathenum
; element is discarded from the pathenum context and the process
; repeated. If there was a path part, then it is resolved.
;
FSPath_StepContext ENTRY "r3"
        MOV     r3, #0
        BL      FSPath_StepCommon
        BLNE    FSPath_ResolveContext
        EXIT

;
; FSPath_StepCommon
;
; In    r0 = pointer to path search context
;       r3 = 0 for step, 1 for sence step
;
; Out   Next element of path is presented if r3=0
;       EQ/NE for done/more
;
; The first element of the pathenum has its first path part
; stripped. If there are no further path parts then the pathenum
; element is discarded from the pathenum context and the process
; repeated. If there was a path part, then it is resolved.
;
FSPath_StepCommon ENTRY "r1,r2,r6"
 [ debugpath
        DREG    r3, "StepCommon:"
 ]
        LDR     r6, [r0]

05
 [ debugpath
        DREG    r6, "Context:"
 ]
        ; Make sure there's more to step
        TEQ     r6, #Nowt
 [ debugpath
        BNE     %FT01
        DLINE   "No more stepping"
01
 ]
        EXIT    EQ

        LDR     r1, [r6, #FSPathEnum_VarVal]
        MOV     r2, r1
 [ debugpath
        DREG    r1,"VarVal remains at ",cc
        DSTRING r1,":"
 ]

        ; Advance r2 to after the next , or to the end of the path
10
        LDRB    r14, [r2], #1
        TEQ     r14, #0
        BEQ     %FT20
        TEQ     r14, #","
        BNE     %BT10

        ; r2 now points past the next , so we'll copy the next element to the start
        TEQ     r3, #0
        BLEQ    strcpy

        TEQ     pc, #0          ; Set NE
 [ debugpath
        DLINE   "Stepping advances"
 ]

        EXIT

20
        ; Found end of path variable - drop this nesting and step the previous nesting
        LDR     r6, [r6, #FSPathEnum_Next]
        TEQ     r3, #0
        BLEQ    SFreeLinkedArea
        TEQ     r3, #0
        STREQ   r6, [r0]
 [ debugpath
        DLINE   "Up-step happened",cc
        BVC     %FT01
        DLINE   " with error"
01
        DLINE   ""
 ]
        EXIT    VS

        B       %BT05

;
; FSPath_AllDone
;
; In    r0 = pointer to path search context
;
; Out   EQ if all done, NE it there's more
;
FSPath_AllDone ENTRY "r3"
        MOV     r3, #1
        BL      FSPath_StepCommon
        EXIT

;
; FSPath_ResolveContext
;
; In    r0 = pointer to path search context
;
; Out   1st element of search context resolved down
;       to unresolvable element.
;       either VS or flags preserved
;
; Given a pathenum context the first element has its path checked
; for starting <path>:. If it does, then <path>$Path is added to the
; context. If <path>$Path didn't exist, then the context is left in
; the same state as it started. If <path>$Path was found then the
; <path>: is stripped and the process repeated on the new first
; element.
;
FSPath_ResolveContext ENTRY "r0,r1,r2,r3,r4,r5,r6"
 [ debugpath
        DLINE   "ResolveContext"
 ]
10
        ; Check for a <path>: prefix on the new path.
        LDR     r6, [r0]
        LDR     r1, [r6, #FSPathEnum_VarVal]
        BL      FSPath_TestForPathLikePrefix
        EXIT    VS
        TEQ     r3, #0
 [ debugpath
        BNE     %FT01
        DLINE   "No prefix"
01
 ]
        EXITS   EQ

        ; Got a path-like prefix

        ; Save pointer past the <path>: bit
        ; and stack adjust
        MOV     r5, r2
        MOV     r6, r3

        ; Add var - hold old 1st path element to check for addition
        LDR     r4, [r0]
        MOV     r2, sp                  ; <path>$Path
        MOV     r3, #NULL               ; No default
 [ debugpath
        DLINE   ".",cc
 ]
        BL      FSPath_AddVarToContext
        ADD     sp, sp, r6              ; Drop the <path>$Path from the stack
        EXIT    VS

        ; Test for addition of a var - return if none was added
        LDR     r2, [r0]
        TEQ     r2, r4
 [ debugpath
        BNE     %FT01
        DLINE   "No var added to context"
01
 ]
        EXITS   EQ

        ; A var was added - remove the <path>: bit from the original path
        MOV     r2, r5
        BL      strcpy

        ; Go round for another go - on the new path
        B       %BT10

;
; FSPath_LengthOfNextPrefix
;
; In    r0 = pointer to pathenum context
;       r3
;
; Out   r3 += length of next prefix in that pathenum context
;
FSPath_LengthOfNextPrefix ENTRY "r0,r1"
        SUB     r0, r0, #FSPathEnum_Next
10
        LDR     r0, [r0, #FSPathEnum_Next]
        TEQ     r0, #Nowt
        EXIT    EQ
        LDR     r1, [r0, #FSPathEnum_VarVal]

        ; strlen_accumulate up to a 0 or a ,
20
        LDRB    r14, [r1], #1
        TEQ     r14, #0
        TEQNE   r14, #","
        ADDNE   r3, r3, #1
        BNE     %BT20

        B       %BT10

;
; FSPath_CopyNextPrefix
;
; In    r0 = pointer to pathenum context
;       r1 = destination for prefix
;
; Out   r1 advanced to end of copied prefix
;
FSPath_CopyNextPrefix ENTRY "r0,r2"
        SUB     r0, r0, #FSPathEnum_Next
10
        LDR     r0, [r0, #FSPathEnum_Next]
        TEQ     r0, #Nowt
        MOVEQ   r14, #0
        STREQB  r14, [r1]
        EXIT    EQ
        LDR     r2, [r0, #FSPathEnum_VarVal]

        ; strcpy_advance up to a 0 or a , (without putting a 0 on the copied result)
20
        LDRB    r14, [r2], #1
        TEQ     r14, #0
        TEQNE   r14, #","
        STRNEB  r14, [r1], #1
        BNE     %BT20

        B       %BT10
 ]
        END
